#!/usr/bin/env python
# -*- coding: utf-8 -*-
import inspect
import os
import tempfile
import threading
import pytest
import allure
import io
import allure_commons

from allure_commons import hookimpl

from utils.login import Login
from commons.logger import log


@pytest.fixture(scope='session')
def admin_login():
    """
    管理员用户登录
    """
    token = Login().login()
    return token


@pytest.fixture(scope='session')
def login():
    """
    普通用户登录
    """
    token = Login(user='user2').login()
    return token



# AllureLogStep类用于在测试步骤执行期间管理日志捕获，并将每个测试步骤对应的日志添加到Allure报告中
class AllureLogStep:
    """
    AllureLogStep类用于管理在测试步骤执行期间的日志捕获，并将每个测试步骤对应的日志添加到Allure报告中。

    主要通过在测试步骤开始和结束时切换日志输出流以及恢复原始流的方式来实现，同时将捕获到的日志信息附加到Allure报告里。
    """

    def __init__(self):
        """
        初始化AllureLogStep实例，这里不再依赖外部传入特定的日志捕获处理器，而是内部管理使用自定义的Log类。
        """
        self._io_stack = []
        self._log_instance = log  # 创建自定义Log类的实例，用于后续记录日志
        self._original_debug = self._log_instance.debug  # 保存原始的debug方法，方便后续切换日志输出流时恢复
        self._original_info = self._log_instance.info
        self._original_warning = self._log_instance.warning
        self._original_error = self._log_instance.error
        self._original_exception = self._log_instance.exception
        self._original_trace = self._log_instance.trace
        self._lock = threading.Lock()

    def redirect_log_to_stream(self, stream, original_log_method, level_name):
        """
        重定向日志输出到指定的内存流，并同时调用原始日志方法。

        参数:
        stream (io.StringIO): 要写入日志的内存流对象，用于临时捕获特定步骤的日志信息。
        original_log_method (function): 原始的日志级别对应的方法（如原始的debug方法等），用于确保日志在写入内存流的同时也按原有逻辑记录到其他地方（如日志文件等）。
        level_name (str): 日志级别名称，如 "DEBUG"、"INFO" 等，用于在写入内存流的日志内容中标识日志级别。

        返回:
        function: 一个包装后的函数，用于替换原有的日志级别方法，实现日志重定向功能。
        """

        def wrapper(msg):
            with self._lock:
                try:
                    frame = inspect.currentframe().f_back
                    module_name = frame.f_globals['__name__']
                    function_name = frame.f_code.co_name
                    stream.write(f"[{level_name}] {module_name}.{function_name}: {msg}\n")
                    original_log_method(msg)
                except Exception as e:
                    error_msg = f"获取日志相关信息出现异常: {e}"
                    stream.write(f"[ERROR] {__name__}.{wrapper.__name__}: {error_msg}\n")
                    original_log_method(msg)

        return wrapper

    @hookimpl
    def start_step(self, uuid, title, params):
        temp_file = tempfile.NamedTemporaryFile(mode='w+', delete=False)
        self._io_stack.append(temp_file)

        def redirect_log(stream, original_log_method, level_name):
            def wrapper(msg):
                with self._lock:
                    try:
                        frame = inspect.currentframe().f_back
                        module_name = frame.f_globals['__name__']
                        function_name = frame.f_code.co_name
                        stream.write(f"[{level_name}] {module_name}.{function_name}: {msg}\n")
                        original_log_method(msg)
                    except Exception as e:
                        print(f"获取日志相关信息出现异常: {e}")
                        original_log_method(msg)
            return wrapper

        self._log_instance.debug = redirect_log(temp_file, self._original_debug, "DEBUG")
        self._log_instance.info = redirect_log(temp_file, self._original_info, "INFO")
        self._log_instance.warning = redirect_log(temp_file, self._original_warning, "WARNING")
        self._log_instance.error = redirect_log(temp_file, self._original_error, "ERROR")
        self._log_instance.exception = redirect_log(temp_file, self._original_exception, "EXCEPTION")
        self._log_instance.trace = redirect_log(temp_file, self._original_trace, "TRACE")

    @hookimpl
    def stop_step(self, uuid, exc_type, exc_val, exc_tb):
        if self._io_stack:
            previous_file = self._io_stack.pop()
            previous_file.seek(0)
            step_logs = previous_file.read().strip()
            try:
                allure.attach(
                    step_logs,
                    name="log",
                    attachment_type=allure.attachment_type.TEXT,
                )
            except Exception as e:
                print(f"将日志添加到Allure报告失败: {e}")
            finally:
                previous_file.close()
                os.remove(previous_file.name)
            self._log_instance.debug = self._original_debug
            self._log_instance.info = self._original_info
            self._log_instance.warning = self._original_warning
            self._log_instance.error = self._original_error
            self._log_instance.exception = self._original_exception
            self._log_instance.trace = self._original_trace


@pytest.hookimpl(trylast=True)
def pytest_configure(config):
    """
    pytest框架的配置钩子函数，用于在测试配置阶段进行一些自定义配置操作，
    这里主要配置日志捕获相关功能，并将AllureLogStep插件注册到Allure框架中，同时添加清理操作。
    """
    step_plugin = AllureLogStep()
    allure_commons.plugin_manager.register(step_plugin)

    def unregister_plugin(plugin):
        """
        定义一个内部函数，用于注销给定的插件，避免资源泄漏和后续干扰。

        :param plugin: 要注销的插件实例。
        """

        def unregister():
            allure_commons.plugin_manager.unregister(plugin)

        return unregister

    config.add_cleanup(unregister_plugin(step_plugin))


if __name__ == '__main__':
    a = admin_login()
    b = login()
    print(a)
    print(b)
